﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace Mclib
{
	/// <summary>
	/// Each instance represents a piece of text drawn onto a surface.
	/// </summary>
	public class Text
	{
		/// <summary>
		/// The location of text in [0, 1] with respect to the container rectangle.
		/// </summary>
		public PointF Location;
		/// <summary>
		/// The font used to draw the axis, size is automatically scaled by DrawScale.
		/// </summary>
		public Font Font
		{
			get { return font; }
			set
			{
				font = value;
				scaledFont = new Font(font.FontFamily,font.Size*drawScale);
			}
		}
		private Font font = new Font(new FontFamily("Arial Narrow"),9.0f);
		private Font scaledFont = new Font(new FontFamily("Arial Narrow"),9.0f);
		/// <summary>
		/// The scale with which to draw the axis.  Automatically scales the font and the pen.
		/// </summary>
		public float DrawScale
		{
			get { return drawScale; }
			set
			{
				if (value!=drawScale)
				{
					drawScale = value;
					scaledFont = new Font(font.FontFamily,font.Size*drawScale);
				}
			}
		}
		private float drawScale = 1.0f;
		/// <summary>
		/// The brush used to draw the text.
		/// </summary>
		public Brush Brush = Brushes.Black;
		public StringFormat Format = StringFormat.GenericDefault;
		public void OnPaint(object sender,PaintEventArgs e)
		{
			throw new NotImplementedException("TO DO");
		}
	}
	/// <summary>
	/// Contains "sender" information in "OnPaint" event handlers.
	/// </summary>
	public class PaintingInfo
	{
		/// <summary>
		/// The sender (parent), usually the object that created this instance.
		/// </summary>
		public object Sender;
		/// <summary>
		/// The rectangle in terms of surface drawing coordinates.
		/// </summary>
		public RectangleF Rect;
		/// <summary>
		/// Create an instance of this class of information that is helpful when drawing inside the client area of a control.
		/// </summary>
		/// <param name="Sender">The sender (parent), usually the object that created this instance.</param>
		/// <param name="Rect">The rectangle in terms of surface drawing coordinates.</param>
		public PaintingInfo(object Sender,RectangleF Rect)
		{
			this.Sender = Sender;
			this.Rect = Rect;
		}
		/// <summary>
		/// Creates a child element instance that occupies a portion of the original rectangle.
		/// </summary>
		/// <param name="u">A rectangle with relative coordinates (LRTB[0,1,0,1]).</param>
		/// <returns></returns>
		public PaintingInfo CreateChild(RectangleF u)
		{
			return new PaintingInfo(this,
				RectangleF.FromLTRB(
					Rect.Left	+Rect.Width	*u.Left,
					Rect.Top	+Rect.Height*u.Top,
					Rect.Right	-Rect.Width	*(1.0f-u.Right),
					Rect.Bottom -Rect.Height*(1.0f-u.Bottom))
			);
		}
	}
	/// <summary>
	/// A class for generating color maps.
	/// </summary>
	public class ColorMap
	{
		/// <summary>
		/// A list of colors where each colorful is useful for tagging a discrete category.
		/// </summary>
		/// <param name="n">The number of unique colors (the length of the output).</param>
		/// <returns>output[iColor][3]  Each iColor color is defined as R,G,B three numbers in the range from 0 to 1.</returns>
		public static double[][] Discrete(int n)
		{
			if (n<=0)
				return null;
			if (n>=8)
				return Wheel(n,false);
			double[][] output = new double[n][];
			if(n==1)
			{
				output[0] = new double[] {0,0,1};
				return output;
			}
			if(n==2)
			{
				output[0] = new double[] {1,0,0};
				output[1] = new double[] {0,0,1};
				return output;
			}
			if(n==3)
			{
				output[0] = new double[] {1,0,0};
				output[1] = new double[] {0,1.0,0};
				output[2] = new double[] {0,0,1};
				return output;
			}
			if(n==4)
			{
				output[0] = new double[] {1,0,1};
				output[1] = new double[] {1,0,0};
				output[2] = new double[] {0,1.0,0};
				output[3] = new double[] {0,0,1};
				return output;
			}
			if(n==5)
			{
				output[0] = new double[] {1,0,1};
				output[1] = new double[] {1,0,0};
				output[2] = new double[] {.5,.5,0};
				output[3] = new double[] {0,1.0,0};
				output[4] = new double[] {0,0,1};
				return output;
			}
			if(n==6)
			{
				output[0] = new double[] {1,0,1};
				output[1] = new double[] {1,0,0};
				output[2] = new double[] {.5,.5,0};
				output[3] = new double[] {0,1.0,0};
				output[4] = new double[] {0,.5,.5};
				output[5] = new double[] {0,0,1};
				return output;
			}
			if(n==7)
			{
				output[0] = new double[] {1,0,1};
				output[1] = new double[] {1,0,0};
				output[2] = new double[] {.8,.3,0};
				output[2] = new double[] {.5,.7,0};
				output[4] = new double[] {0,1.0,0};
				output[5] = new double[] {0,.6,.6};
				output[6] = new double[] {0,0,1};
				return output;
			}
			return output;
		}
		/// <summary>
		/// A continuum of n colors transitioning through
		///		purple
		///		blue
		///		green
		///		white --> Only when middleIsWhite is true
		///		yellow
		///		orange
		///		red
		/// </summary>
		/// <param name="n">The number of colors (the length of the output).</param>
		/// <param name="middleIsWhite">If true, the center of the color continuum goes to white.</param>
		/// <returns>output[iColor][3]  Each iColor color is defined as R,G,B three numbers in the range from 0 to 1.</returns>
		public static double[][] Wheel(int n, bool middleIsWhite)
		{
			if (n<8)
				return Discrete(n);
			double[][] output = new double[n][];
			double[] col;
			int highNeg = (int)(0.3*(double)n);
			int lowPos = (int)Math.Ceiling(0.7*(double)n);
			double fac = 1.8*Math.PI/(double)n;
			double c,s,theta;
			double wfac = 1.0/(double)(n-1);
			double iw,w;
			int i;

			//	For each color
			for(i=0; i<n; i++)
			{
				col = new double[3];
				output[i] = col;

				theta = ((double)i+0.5)*fac;
				c = Math.Cos(theta);
				s = Math.Sin(theta);
				if(s>0.0) // yellow
				{
					//col[2] = 0.0;
					if(c>0.0) // red
					{
						col[0] = Math.Min(1.0,c+s);
						col[1] = s;
					}
					else // c<=0.0 green
					{
						col[0] = s;
						col[1] = Math.Min(1.0,s-c);
					}
				}
				else // s<=0.0 blue
				{
					col[2] = -s;
					if(c>0.0) //	red
					{
						col[0] = c;
						//col[1] = 0.0;
					}
					else // c<=0.0 green
					{
						//col[0] = 0.0;
						col[1] = -c;
					}
				}
				if( middleIsWhite && i>highNeg && i<lowPos )
				{
					//	The extent to which the color is NOT white (from 0 to 1).
					iw = Math.Abs(5.0*((double)i*wfac-0.5));
					//	The extent to which the color is white.
					w = 1.0-iw;
					//	Blend white.
					col[0] = col[0]*iw + w;
					col[1] = col[1]*iw + w;
					col[2] = col[2]*iw + w;
				}
			}
			return output;
		}
		/// <summary>
		/// Returns a de-saturated color.
		/// </summary>
		/// <param name="col">double[3] elements are in the range [0.0,1.0]</param>
		/// <param name="u">a number in the range [0.0,1.0].  0.0 is desaturated, 1.0 is white.</param>
		/// <returns>double[3] elements are in the range [0.0,1.0]</returns>
		public static double[] Desaturate(double[] col, double u)
		{
			double uc = 1.0-u;
			return new double[] { col[0]*uc+u, col[1]*uc+u, col[2]*uc+u };
		}
		/// <summary>
		/// Returns a de-saturated color.  The whitest point is limited to [1.0,0.75,1.0].  This prevents the
		/// green gun from going too high because pure green is hard to discriminate from white.
		/// </summary>
		/// <param name="col">double[3] elements are in the range [0.0,1.0]</param>
		/// <param name="u">a number in the range [0.0,1.0].  0.0 is desaturated, 1.0 is [1.0,0.75,1.0].</param>
		/// <returns>double[3] elements are in the range [0.0,1.0]</returns>
		public static double[] DesaturateAndLimit(double[] col, double u)
		{
			double[] ans = Desaturate(col,u);
			ans[1] *=0.75;
			return ans;
		}
		/// <summary>
		/// Converts a double[3] to a Color data structure.
		/// </summary>
		/// <param name="col">double[3] elements are in the range [0.0,1.0]</param>
		/// <returns>A Color data structure.</returns>
		public static Color FromDouble(double[] col)
		{
			return Color.FromArgb(
				(int)(col[0]*255.99999), 
				(int)(col[1]*255.99999), 
				(int)(col[2]*255.99999) );
		}
	}
}